﻿var sandScript = sandScript || {};

//#region Module Documentation

sandScript.constructor = function () {
    /// <field name="state" type="Array" elementType="String">Stores the state of the global object before custom variables are added.</field>
    /// <field name="tools" type="Object">Module containing generic helpers.</field>
    /// <field name="reflector" type="Object">Module containing JavaScript reflection and VSDoc extraction routines.</field>

    'use strict';
};

//#endregion

sandScript.reflector = (function () {
    'use strict';

    var m = {},
        tools = sandScript.tools,
        patterns;

    patterns = {
        paramList: new RegExp('^function[^\\(]*\\(([^\\)]*)\\)'),
        emptyLine: new RegExp('^\\s*$'),
        docLine: new RegExp('^\\s*///(.+)'),
        paramDivider: new RegExp('\\s*,\\s*')
    };

    //#region Encapsulated Methods

    function getFunctionParams(source) {
        var paramString,
            match = patterns.paramList.exec(source);

        if (match && match.length === 2) {
            paramString = match[1].trim();

            if (paramString) {
                return paramString.split(patterns.paramDivider);
            }
        }

        return [];
    }

    function getFunctionDoc(source) {
        var lines,
            match,
            documentation = '';

        lines = source
            .substring(source.indexOf('{') + 1, source.length - 1)
            .split('\n');

        function processLine(line) {
            if (!patterns.emptyLine.test(line)) {
                match = patterns.docLine.exec(line);

                if (match && match.length === 2) {
                    documentation += match[1].trim();
                } else {
                    return false;
                }
            }

            return true;
        }

        lines.every(processLine);

        return documentation;
    }

    function mapToInfo(key) {
        var doc,
            descriptor = Object.getOwnPropertyDescriptor(this, key); //ignore jslint

        if (tools.isDataDescriptor(descriptor)) {
            if (tools.isTypeOf(descriptor.value, 'f')) {
                doc = new FunctionInfo(key, descriptor); //ignore jslint
            } else if (tools.isObjectType(descriptor.value)) {
                doc = new ObjectInfo(key, descriptor); //ignore jslint
            } else {
                doc = new PrimitiveInfo(key, descriptor); //ignore jslint
            }
        } else {
            doc = new PropertyInfo(key, descriptor); //ignore jslint
        }

        return doc;
    }

    function filterByKey(key) {
        return key !== 'constructor' && key !== 'prototype' && isNaN(key);
    }

    function filterByState(key) {
        return sandScript.state.indexOf(key) < 0;
    }

    function getMemberDocs(value, filterMethod) {
        if (!tools.isTypeOf(filterMethod, 'f')) {
            filterMethod = filterByKey;
        }

        return Object
            .keys(value)
            .filter(filterMethod)
            .map(mapToInfo, value);
    }

    //#endregion

    //#region Encapsulated Constructors

    function BaseInfo(type, name, writable, valueType) {
        this.type = type;
        this.name = name;
        this.writable = writable;
        this.valueType = valueType;
    }

    function PrimitiveInfo(name, descriptor) {
        tools.constructFrom(this, BaseInfo, ['primitive', name, descriptor.writable, tools.objectTypeOf(descriptor.value)]);

        this.value = descriptor.value;
    }

    tools.prototypeFrom(PrimitiveInfo, BaseInfo);

    function ObjectInfo(name, descriptor) {
        tools.constructFrom(this, BaseInfo, ['object', name, descriptor.writable, tools.objectTypeOf(descriptor.value)]);

        this.documentation = getFunctionDoc(descriptor.value.constructor.toString());
        this.members = getMemberDocs(descriptor.value);
    }

    tools.prototypeFrom(ObjectInfo, BaseInfo);

    function FunctionInfo(name, descriptor) {
        tools.constructFrom(this, BaseInfo, ['function', name, descriptor.writable, 'Function']);

        var source = descriptor.value.toString();

        this.documentation = getFunctionDoc(source);
        this.parameterLength = descriptor.value.length;
        this.parameters = getFunctionParams(source);
        this.members = getMemberDocs(descriptor.value);
        this.prototype = getMemberDocs(descriptor.value.prototype);
    }

    tools.prototypeFrom(FunctionInfo, BaseInfo);

    function PropertyInfo(name, descriptor) {
        var writable = tools.isTypeOf(descriptor.set, 'f');

        tools.constructFrom(this, BaseInfo, ['property', name, writable]);

        this.documentation = '';
        this.readable = tools.isTypeOf(descriptor.get, 'f');

        if (this.readable) {
            this.documentation = getFunctionDoc(descriptor.get.toString());
        } else if (writable) {
            this.documentation = getFunctionDoc(descriptor.set.toString());
        }
    }

    tools.prototypeFrom(PropertyInfo, BaseInfo);

    //#endregion

    //#region Exposed Methods

    m.reflectByName = function (names) {
        /// <summary>Builds an array of reflection information objects for a given list of global objects.</summary>
        /// <param name="names" type="Array" elementType="String">The list of global scoped variables to use as the starting point for documentation.</param>
        /// <returns type="Array" elementType="Object" />

        function filterByName(key) {
            return names.indexOf(key) !== -1;
        }

        if (tools.isTypeOf(names, 'a')) {
            return getMemberDocs(window, filterByName);
        }

        return [];
    };

    m.reflectAll = function () {
        /// <summary>Builds an array of reflection information objects for all global objects that were not present when sandScript.state was created.</summary>
        /// <returns type="Array" elementType="Object" />

        if (tools.isTypeOf(sandScript.state, 'a')) {
            return getMemberDocs(window, filterByState);
        }

        return [];
    };

    //#endregion

    return m;
}());
